/*
 * HMAC(Hash-based Message Authentication Code)
 *
 * Algorithm Description:
 *   HMAC(K，M) = H((K’ XOR OPAD) | H((K’ XOR IPAD) | M))
 * 
 */
#include "gkt_debug.h"
#include "gkt_crypto.h"
#include "gkt_malloc.h"
#include "gkt_hmac.h"

#define HMAC_IPAD_VALUE	0x36
#define HMAC_OPAD_VALUE	0x5C

typedef int (*hmac_hash_fn)(const uint8_t *message, 
				uint32_t msg_len, uint8_t *hash);

typedef struct _hmac_attr {
	uint32_t	block_size;
	uint32_t	digest_size;
	hmac_hash_fn	hash_fn;
} hmac_attr_s;
static const hmac_attr_s sc_hmac_attrs[GKT_HMAC_MAX] = {
	{ 
		GKT_CRYPTO_SHA1_BLOCK_BYTES, 
		GKT_CRYPTO_SHA1_DIGEST_BYTES,
		gkt_crypto_sha1
	},
	{ 
		GKT_CRYPTO_SHA256_BLOCK_BYTES, 
		GKT_CRYPTO_SHA256_DIGEST_BYTES,
		gkt_crypto_sha256
	}
};

int gkt_hmac(gkt_hmac_type_e type,
		const uint8_t *key, uint32_t key_len,
		const uint8_t *msg, uint32_t msg_len,
		uint8_t *hash)
{
	const hmac_attr_s *hmac_attr;
	uint8_t *buffer, *aligned_key, *message;
	uint32_t offset, max_len;
	int retval;

	if ((type < GKT_HMAC_MAX) 
		&& key && (key_len > 0) 
		&& msg && (msg_len > 0))
	{
		hmac_attr = &sc_hmac_attrs[type];

		max_len = hmac_attr->block_size 
					+ hmac_attr->block_size
					+ gkt_max(msg_len, hmac_attr->digest_size);
		buffer = (uint8_t *)gkt_malloc(max_len);
		if (!buffer)
			return GKT_ENOMEMORY;

		aligned_key = buffer;
		if (key_len > hmac_attr->block_size) {
			retval = (*hmac_attr->hash_fn)(key, key_len, aligned_key);
			if (__UNLIKELY(retval != GKT_SUCCESS))
				return retval;
			offset = hmac_attr->digest_size;
		}
		else {
			for (offset = 0; offset < key_len; offset++)
				aligned_key[offset] = key[offset];
		}
		while (offset < hmac_attr->block_size)
			aligned_key[offset++] = 0;
		
		message = aligned_key + hmac_attr->block_size;
		for (offset = 0; offset < hmac_attr->block_size; offset++)
			message[offset] = aligned_key[offset] ^ HMAC_IPAD_VALUE;
		for (offset = 0; offset < msg_len; offset++)
			message[hmac_attr->block_size + offset] = msg[offset];
		retval = (*hmac_attr->hash_fn)(message, 
						hmac_attr->block_size + msg_len, 
						hash);
		if (__UNLIKELY(retval != GKT_SUCCESS)) {
			gkt_error("hmac: [1] hash failed %d\n", retval);
			goto done;
		}

		for (offset = 0; offset < hmac_attr->block_size; offset++)
			message[offset] = aligned_key[offset] ^ HMAC_OPAD_VALUE;
		for (offset = 0; offset < hmac_attr->digest_size; offset++)
			message[hmac_attr->block_size + offset] = hash[offset];
		retval = (*hmac_attr->hash_fn)(message, 
						hmac_attr->block_size + hmac_attr->digest_size, 
						hash);
		if (__UNLIKELY(retval != GKT_SUCCESS))
			gkt_error("hmac: [2] hash failed %d\n", retval);

done:
		gkt_free(buffer);
	}
	else
		retval = GKT_EPARAM;

	return retval;
}

